package cn.git.shiro.filter;

import cn.git.shiro.constant.ShiroJWTConstant;
import cn.git.shiro.result.Result;
import cn.git.shiro.token.JwtToken;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/** 
 * @description: 自定义shiro-jwt过滤器
 * 方法执行顺序 : preHandle > isAccessAllowed > isLoginAttempt > executeLogin > onAccessDenied
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2024-03-27
 */
@Component
public class CustomJWTFilter extends BasicHttpAuthenticationFilter {

    /**
     * 线程变量
     */
    private static final ThreadLocal<Exception> lastException = new ThreadLocal<>();

    /**
     * 获取上下文路径
     */
    @Value("${server.servlet.context-path}")
    private String contextPath;

    /**
     * 判定请求是否带有Token，如果没有则直接放行
     * @param request 请求
     * @param response 响应
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 判断用户是否是登入
        if (this.isLoginAttempt(request, response)) {
            return true;
        }
        try {
            // 进行自定义登录
            this.executeLogin(request, response);
            return true;
        } catch (Exception e) {
            lastException.set(e);
            return false;
        }
    }

    /**
     * 登录
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        // 获取请求token信息
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(ShiroJWTConstant.JWT_TOKEN_FLAG);
        JwtToken jwtToken = new JwtToken(token);
        // 自定义realm登录
        super.getSubject(request, response).login(jwtToken);
        return true;
    }

    /**
     * 判断是否是登录请求
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        // 获取请求地址信息
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String url = httpServletRequest.getRequestURI();
        String token = httpServletRequest.getHeader(ShiroJWTConstant.JWT_TOKEN_FLAG);
        // 判断是否是登录请求
        if (contextPath.concat(ShiroJWTConstant.LOGIN_PATH).equals(url)) {
            return true;
        }
        // 登录请求token信息为空
        return StrUtil.isBlank(token);
    }

    /**
     * 当访问被拒绝时的处理逻辑。
     *
     * @param request  Servlet请求对象，用于获取客户端请求信息。
     * @param response Servlet响应对象，用于向客户端发送响应。
     * @return 总是返回false，表示访问被拒绝且不会尝试其他拦截器或过滤器。
     * @throws Exception 抛出异常，可用于处理访问被拒绝时的各类异常情况。
     */
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        // 设置响应状态码为访问拒绝的自定义状态码
        httpServletResponse.setStatus(ShiroJWTConstant.ON_ACCESS_DENIED_NUM);
        // 设置响应内容类型为JSON
        httpServletResponse.setContentType(ShiroJWTConstant.JSON_CONTENT_TYPE);
        PrintWriter out = httpServletResponse.getWriter();
        // 获取并输出最后的异常信息
        Exception exception = lastException.get();
        // 清除最后的异常信息
        lastException.remove();
        out.println(JSONUtil.toJsonStr(Result.error(exception.getCause().getMessage())));
        out.flush();
        out.close();
        return false;
    }

    /**
     * 跨域请求支持
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization,token, content-type"); //这里要加上content-type
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        // 跨域时会首先发送一个option请求，这里给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

}
